#ifndef BLOCK_H
#define BLOCK_H
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/numeric/mtl/mtl.hpp>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <vector>
#include <algorithm>
#include <utility>
#include <map>
#include <cmath>
#include "configure.h"
#include "siftgeo.h"
#include "quantizer.h"
namespace bfs = boost::filesystem;
using namespace std;
struct BlockRecord
{
    int termid;
    int imageid;
    float hashsift[HAMMING_DIM];
    // float scale;
    // float angle;
};
int brcmp(const BlockRecord& br1, const BlockRecord& br2 )
{
    if(br1.termid == br2.termid)
        return br1.imageid < br2.imageid;
    return br1.termid < br2.termid;
}
class Block
{
    private:
    BlockRecord* brbuf;
    unsigned int bufsize;
    unsigned int len;
    int cur_blockid;
    string blockpath;
    public:
    Block()
    {
    }
    Block(const int _bufsize,const int _cur_blockid,const string _blockpath)
    {
        cout << " block constructor " << endl;
        bufsize = _bufsize;
        brbuf = new BlockRecord[bufsize];
        len = 0;
        blockpath = _blockpath;
        cur_blockid = _cur_blockid;
    }
    ~Block()
    {
        cout << "block destructor " << endl;
    }
    void push(const BlockRecord& br)
    {
        if(len == bufsize)
        flush();
        memcpy(brbuf + len, &br,sizeof(BlockRecord));
        ++len;
    }
    void flush()
    {
        cout << "flush block " << cur_blockid << " to disk" << endl;
        char name[100];
        sprintf(name,"%sblock_%06d",blockpath.c_str(),cur_blockid);
        tofile(name);
        len = 0;
        ++cur_blockid;
        cout << " flush complete !!! " << endl;
    }
    void tofile(string blockname)
    {
        sort(brbuf,brbuf+len,brcmp);
        FILE* fid = fopen(blockname.c_str(),"w");
        cout << len << endl;
        fwrite(reinterpret_cast<char*>(brbuf),sizeof(BlockRecord),len,fid);
        fclose(fid);
    }
    void free()
    {
        delete[] brbuf;
    }
};
bool checkblock(string blockname)
{
        struct stat statbuf;
        stat(blockname.c_str(),&statbuf);
        int fid = open(blockname.c_str(),O_RDONLY);
        char *back_buf = reinterpret_cast<char*>(mmap(NULL,statbuf.st_size,PROT_READ,MAP_PRIVATE,fid,0));
        int len = *(reinterpret_cast<int*>(back_buf));
        BlockRecord* br = reinterpret_cast<BlockRecord*>(back_buf+4);
        for(int i=0; i<len-1; ++i,++br)
        {
           if(br->termid > (br+1)->termid ) return false;
           if(br->termid == (br+1)->termid && br->imageid > (br+1)->imageid)return false;
        }
        munmap(back_buf,statbuf.st_size);
        return true;
}
class BlockManager
{
    private:
    Block block;
    vector<string>imagelist;
    vector<int>imagelength;
    SiftGeo sg;
    Quantizer qz;
    public:
    BlockManager(int blockid)
    {
        cout << " blockmanager constructor " << endl;
        block = Block(MAXRECORDS_PER_BLOCK,blockid,BLOCK_PATH);
        qz = Quantizer(CLUST_PATH,HAMATRIX_PATH);
    }
    ~BlockManager()
    {
        cout << " blockmanager destructor " << endl;
    }
    int addimage(string filename,int imageid)
    {
        cout<< " add " << filename << " to block " << endl;
        BlockRecord br;
        sg.fromfile(filename);
        qz.findnn(sg.sift,sg.count);
        qz.gen_hashsift(sg.sift,sg.count);
        br.imageid = imageid;
        float* hashsift = qz.hashsift;
        for(int i=0; i<sg.count; ++i)
        {
            br.termid = qz.nn[i];
            memcpy(br.hashsift,hashsift,HAMMING_DIM*sizeof(float));
            hashsift += HAMMING_DIM;
            block.push(br);
        }
        return sg.count;
    }
    void makeblocks(int imageid = 0)
    {
        bfs::path siftgeodir(SIFTGEO_PATH);
        int imagenum = 0;
        for(bfs::directory_iterator dit(siftgeodir); dit!= bfs::directory_iterator(); ++dit)
        {
            if(bfs::extension(*dit)!=".siftgeo")continue;
            ++imagenum;
        }
        cout << " total " << imagenum << " images " << endl;

        imagelist = vector<string>(imagenum);
        imagelength = vector<int>(imagenum);

        int imi = 0;
        for(bfs::directory_iterator dit(siftgeodir); dit!=bfs::directory_iterator(); ++dit)
        {
            if(bfs::extension(*dit)!=".siftgeo")continue;
            imagelist[imi] = dit->filename();
            imagelength[imi] = addimage(dit->string(),imageid);
            ++imi;
            ++imageid;
        }
        block.flush();
        cout <<  "writing imagelist " << endl;
        FILE* fid = fopen("imagelist.txt","w");
        for(int i=0; i<imagelist.size(); ++i)
            fputs((imagelist[i]+"\n").c_str(),fid);
        fclose(fid);
        cout << "writing imagelength " << endl;
        fid = fopen("imagelength.mat","w");
        fwrite(reinterpret_cast<char*>(&imagenum),sizeof(int),1,fid);
        fwrite(reinterpret_cast<char*>(&imagelength[0]),sizeof(int),imagelength.size(),fid);
        fclose(fid);
    }
    void free()
    {
        block.free();
        vector<string>().swap(imagelist);
        vector<int>().swap(imagelength);
        sg.free();
        qz.free();
    }
};
#endif
